/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package beans;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Asteroidz
 */
public class MySqlEntity {

/* ~~~~~~~~~~ FONCTIONS ~~~~~~~~~~ */
    /**
     * Permet d'obtenir les noms de toutes les DataBases
     *
     * @param co -> (Connection) connexion MySQL
     * @return -> (List:String) une liste contenant le nom de toutes les
     * DataBases
     */
    public static List<String> getAllDataBases(Connection co) {
        // List qui va réceptionner chaque nom de DataBase
        List<String> dataBases = new ArrayList<>();

        try {
            // on execute la requête SQL pour obtenir un ResultSet
            Statement stmt = co.createStatement();
            String sql = "SHOW DATABASES";
            ResultSet res = stmt.executeQuery(sql);

            // Pour chaque ligne du résultat de la requête,
            // on stock le nom de la DataBase dans la liste
            while (res.next()) {
                dataBases.add(res.getString(1));
            }
        } catch (SQLException e) {
            System.out.println(e.toString());
        }
        return dataBases;
    }

    /**
     * Permet d'obtenir les noms de toutes les tables d'une DataBase
     *
     * @param co -> (Connection) connexion MySQL
     * @return -> (List:String) une liste contenant les noms de toutes les
     * tables
     */
    public static List<String> getAllTables(Connection co) {
        // List utilisée pour stocker le nom de toutes les tables
        List<String> Tables = new ArrayList<>();

        try {
            // On execute la requête SQL pour obtenir un ResultSet
            Statement stmt = co.createStatement();
            String sql = "SHOW TABLES";
            ResultSet res = stmt.executeQuery(sql);

            // Pour chaque ligne du résultat de la requête,
            // on stock le nom de la table dans la liste
            while (res.next()) {
                Tables.add(res.getString(1));
            }
        } catch (SQLException e) {
            System.out.println(e.toString());
        }
        return Tables;
    }

    /**
     * Permet d'obtenir le nom de la première table d'une DataBase
     *
     * @param co -> (Connection) Connexion MySQL
     * @return -> (String) le nom de la première table
     */
    public static String getFirstTable(Connection co) {
        String tableName = "";

        try {
            // on execute la requête SQL qui va chercher le nom de toutes les tables
            Statement stmt = co.createStatement();
            String sql = "SHOW TABLES";
            ResultSet res = stmt.executeQuery(sql);

            // on parcours le ResultSet mais on ne va prendre uniquement la première entrée
            // Pour stocker le nom de la première table dans la variable
            int c = 0;
            while (res.next()) {
                if (c >= 1) {
                    break;
                }
                tableName = res.getString(1);
                c++;
            }
        } catch (SQLException e) {
            System.out.println(e.toString());
        }

        return tableName;
    }

    /*
     * Change la DataBase qui sera utilisée
     *
     * @param co -> (Connection) Connexion MySQL
     * @param db -> (String) Nom de la Database qui va être utilisée
     */
    public static void switchDataBase(Connection co, String db) {
        try {
            // execute la requête pour changer la DataBase qu'on utilise
            Statement stmt = co.createStatement();
            ResultSet res = stmt.executeQuery("USE " + db);
            
            // on femre le Statement et le ResultSet
            stmt.close();
            res.close();
        } catch (SQLException e) {
            System.out.println(e.toString());
        }
    }
    
    /**
     * Permet d'obtenir une description de la table choisie
     * 
     * @param co    -> (Connection) Connexion MySQL
     * @param table -> (String) Nom de la table choisie
     * @return      -> (LinkedHashMap:String, List(Object)) une LinkedHashMap contenant la description de la table
     */
    public static int getCountTable(Connection co, String table) {
        int count = 0;
        try {
            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM " + table);

            while (resultSet.next()) {
                count = resultSet.getInt(1);
            }

        } catch (SQLException ex) {
            Logger.getLogger(MySqlEntity.class.getName()).log(Level.SEVERE, null, ex);
        }
        return count;
    }

    public static LinkedHashMap<String, List<Object>> getTableDesc(Connection co, String table) {
        LinkedHashMap<String, List<Object>> tableDesc = new LinkedHashMap<>();
        
        try {
            // requête pour avoir la description de la table choisie
            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet resultSet = statement.executeQuery("DESC " + table);
            
            // ResultSetMetaData utilisé pour avoir des infos sur les colonnes 
            ResultSetMetaData rsmd = resultSet.getMetaData();
            int nbColumns = rsmd.getColumnCount();
            
            // pour chaque colonne, on la stock dans une List
            List<String> columnsName = new ArrayList<>();
            for (int i = 1; i <= nbColumns; i++) {
                columnsName.add(rsmd.getColumnLabel(i));
                tableDesc.put(rsmd.getColumnLabel(i), new ArrayList<>());
            }

            // Pour chaque nom de colonne, on stock sa valeur dans une LinkedHashMap
            while (resultSet.next()) {
                for (int i = 1; i <= columnsName.size(); i++) {
                    List<Object> content = tableDesc.get(columnsName.get(i - 1));
                    Object ins = resultSet.getObject(i);
                    content.add(ins);
                    tableDesc.put(columnsName.get(i - 1), content);
                }
            }

        } catch (SQLException ex) {
            Logger.getLogger(MySqlEntity.class.getName()).log(Level.SEVERE, null, ex);
        }
        return tableDesc;
    }

    /**
     * Stock tout le contenue d'une table (équivalent d'un select *)
     *
     *
     * @param co -> (Connection) connexion MySQL
     * @param table -> (String) Le nom de la table choisie
     * @return -> (LinkedHashMap:String, List(Object)) une LinkedHashMap qui
     * possède le contenu de la table
     */
    public static LinkedHashMap<String, List<Object>> getTableContent(Connection co, String table, int nb) {
        // Map qui va réceptionner tout le contenu de la table
        LinkedHashMap<String, List<Object>> tableContent = new LinkedHashMap<>();

        try {
            // requête SQL pour choper le contenu d'une table
            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet resultSet = statement.executeQuery("SELECT * FROM " + table + " LIMIT " + nb + ",30");

            // ResultSetMetaData utilisé pour avoir des infos sur les colonnes du ResultSet
            ResultSetMetaData rsmd = resultSet.getMetaData();
            int nbColumns = rsmd.getColumnCount();
            
            tableContent.put("Controls_button_jsf_mod_del", new ArrayList<>());
            List<String> columnsName = new ArrayList<>();
            List<Object> columnsButton = new ArrayList<>();

            // Pour chaque colonne du ResultSet
            for (int i = 1; i <= nbColumns; i++) {
                // on ajoute le nom de la colonne à la List, et on l'ajoute en tant que clef de la Map
                columnsName.add(rsmd.getColumnLabel(i));
                tableContent.put(rsmd.getColumnLabel(i), new ArrayList<>());
            }

            // Pour chaque ligne du ResultSet
            int c = 1;
            while (resultSet.next()) {
                // on parcours toutes les colonnes
                for (int i = 1; i <= columnsName.size(); i++) {
                    // on stock toutes les infos de la ligne, et on l'insère dans la Map
                    List<Object> content = tableContent.get(columnsName.get(i - 1));
                    Object ins = resultSet.getObject(i);
                    content.add(ins);
                    tableContent.put(columnsName.get(i - 1), content);
                }
                // on ajoute les boutons pour modifier ou supprimer
                columnsButton.add("<a href=\"modify.xhtml?mtable=" + table + "&mid=" + c + "&maction=modify\" class=\"btn green\">modify</a><a href=\"delete.xhtml?mtable=" + table + "&mid=" + c + "&maction=delete\" class=\"btn red\">delete</a>");
                c++;
            }

            tableContent.put("Controls_button_jsf_mod_del", columnsButton);
        } catch (SQLException ex) {
            Logger.getLogger(MySqlEntity.class.getName()).log(Level.SEVERE, null, ex);
        }
        return tableContent;
    }

    /**
     * Modifie le contenu de la ligne d'une table
     * 
     * @param co       -> (Connection) connexion MySQL
     * @param db       -> (String) le nom de la DataBase
     * @param table    -> (String) le nom de la table
     * @param hm       -> (LinkedHashMap:String, Object) LinkedHashMap contenant le contenu de la ligne
     * @param position -> (int) position dans le ResultSet
     * @return         -> (String) Le message d'erreur SQL (s'il y en a un)
     */
    public static String updateContent(Connection co, String db, String table, LinkedHashMap<String, Object> hm, int position, int nb) {
        String errMsg = "Contenu bien enregistré";
        try {
            // requête basique
            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet rs = statement.executeQuery("SELECT * FROM " + table + " LIMIT " + nb + ",30");

            // ResultSetMetaData utilisé pour obtenir des infos sur les colonnes
            ResultSetMetaData resultMeta = rs.getMetaData();

            // petite String qui va stocker le nom de la colonne PK (s'il y en a une !)
            String Pkey = "";

            // Pour chaque colonne du ResultSet
            for (int i = 1; i <= resultMeta.getColumnCount(); i++) {
                // si la colonne est auto increment, alors on considère que c'est la PK
                boolean isAutoincrement = resultMeta.isAutoIncrement(i);
                if (isAutoincrement == true) {
                    Pkey = resultMeta.getColumnName(i);
                }
            }

            // on bouge le curseur du ResultSet sur la position choisie
            rs.absolute(position);

            // Pour chaque entrée de la Map qui a le contenu de la table
            for (Map.Entry<String, Object> entry : hm.entrySet()) {
                String k = entry.getKey();
                Object v = entry.getValue();
                if (!k.equals(Pkey)) {
                    rs.updateString(k, v.toString());
                }
            }

            rs.updateRow();
        } catch (SQLException ex) {
            errMsg = ex.toString();
        }
        return errMsg;
    }

    /**
     * Insère une nouvelle ligne en base de données
     * 
     * @param co    -> (Connection) connexion MySQL
     * @param db    -> (String) le nom de la DataBase
     * @param table -> (String) le nom de la table
     * @param hm    -> (LinkedHashMap:String, Object) LinkedHashMap contenant le contenu de la ligne à insérer
     * @return      -> (String) Le message d'erreur SQL (s'il y en a un)
     */
    public static String insertContent(Connection co, String db, String table, LinkedHashMap<String, Object> hm) {
        String errMsg = "Contenu bien inséré";
        try {
            // requête basique
            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet rs = statement.executeQuery("SELECT * FROM " + table + " LIMIT 1");
            
            // ResultSetMetaData pour avoir des infos sur les colonnes
            ResultSetMetaData resultMeta = rs.getMetaData();

            String Pkey = "";

            // pour chaque colonne, si c'est une PK, on stock le nom de la colonne dans une String
            for (int i = 1; i <= resultMeta.getColumnCount(); i++) {
                boolean isAutoincrement = resultMeta.isAutoIncrement(i);
                if (isAutoincrement == true) {
                    Pkey = resultMeta.getColumnName(i);
                }
            }
            
            // on déplace le curseur sur la ligne a insérer
            rs.moveToInsertRow();
            
            // pour chaque entrée de la LinkenHashMap
            for (Map.Entry<String, Object> entry : hm.entrySet()) {
                String k = entry.getKey();
                Object v = entry.getValue();
                
                //si ce n'est pas la clef primaire, on peut modifier la valeur
                if (!k.equals(Pkey)) {
                    rs.updateString(k, v.toString());
                }
            }

            rs.insertRow();
        } catch (SQLException ex) {
            errMsg = ex.toString();
        }
        return errMsg;
    }

    /**
     * Supprime une ligne choisie en base de données
     * 
     * @param co       -> (Connection) connexion MySQL
     * @param db       -> (String) le nom de la DataBase
     * @param table    -> (String) le nom de la table
     * @param hm       -> (LinkedHashMap:String, Object) LinkedHashMap contenant le contenu de la ligne
     * @param position -> (int) position dans le ResultSet
     * @return         -> (String) Le message d'erreur SQL (s'il y en a un)
     */
    public static String deleteContent(Connection co, String db, String table, LinkedHashMap<String, Object> hm, int position, int nb) {
        String errMsg = "Suppression bien enregistrée";
        try {
            // requête basique
            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet rs = statement.executeQuery("SELECT * FROM " + table + " LIMIT " + nb + ",30");
            rs.absolute(position);

            // supprime la ligne
            rs.deleteRow();

            // commit dans la base de données
            rs.updateRow();

        } catch (SQLException ex) {
            errMsg = ex.toString();
        }
        return errMsg;
    }

    /**
     * Récupère les données sur les Foreign Key
     * 
     * @param co    -> (Connection) connexion MySQL
     * @param db    -> (String) nom de la DataBase
     * @param table -> (String) nom de la table
     * @return      -> (LinkedHashMap:String, String) LinkedHashMap contenant les foreign key de la table en cours
     */
    public static LinkedHashMap<String, String> getTableForeignKey(Connection co, String db, String table) {
        // Map qui va réceptionner tout le contenu de la table
        LinkedHashMap<String, String> foreignKey = new LinkedHashMap<>();
        try {

            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet resultSet = statement.executeQuery("DESC " + table);

            while (resultSet.next()) {
                String cn = resultSet.getString(1);
                String fk = resultSet.getString(4);
                if(fk.equals("MUL")){
                    foreignKey.put(cn, fk);
                }
            }

        } catch (SQLException ex) {
            Logger.getLogger(MySqlEntity.class.getName()).log(Level.SEVERE, null, ex);
        }
        return foreignKey;
    }
    
    /**
     * Récupère la valeur des Foreign Key
     * 
     * @param co    -> (Connection) connexion MySQL
     * @param table -> (String) nom de la table
     * @param field -> (String) nom de la colonne
     * @return      -> (List:String) List contenant les valeurs des foreign key de la table en cours
     */
    public static List<String> getFKcontent(Connection co, String table, String field){
        List<String> ls = new ArrayList<>();
        try {

            Statement statement = co.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ResultSet rs = statement.executeQuery("SELECT DISTINCT " + field + " FROM " + table);

            while (rs.next()) {
                ls.add(rs.getString(1));
            }

        } catch (SQLException ex) {
            Logger.getLogger(MySqlEntity.class.getName()).log(Level.SEVERE, null, ex);
        }
        return ls;
    }
}
